'use client';
import * as React from 'react';
import { useEffectAfterFirstRender } from '@mui/x-internals/useEffectAfterFirstRender';
import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
import ownerWindow from '@mui/utils/ownerWindow';
import { useSelector } from '../../../store/useSelector';
import { DEFAULT_MARGINS } from '../../../../constants';
import { type ChartPlugin } from '../../models';
import type { UseChartDimensionsSignature } from './useChartDimensions.types';
import { selectorChartDrawingArea } from './useChartDimensions.selectors';
import { defaultizeMargin } from '../../../defaultizeMargin';

const MAX_COMPUTE_RUN = 10;

export const useChartDimensions: ChartPlugin<UseChartDimensionsSignature> = ({
  params,
  store,
  svgRef,
}) => {
  const hasInSize = params.width !== undefined && params.height !== undefined;
  const stateRef = React.useRef({ displayError: false, initialCompute: true, computeRun: 0 });
  // States only used for the initialization of the size.
  const [innerWidth, setInnerWidth] = React.useState(0);
  const [innerHeight, setInnerHeight] = React.useState(0);

  const computeSize = React.useCallback(() => {
    const mainEl = svgRef?.current;

    if (!mainEl) {
      return {};
    }

    const win = ownerWindow(mainEl);
    const computedStyle = win.getComputedStyle(mainEl);

    const newHeight = Math.floor(parseFloat(computedStyle.height)) || 0;
    const newWidth = Math.floor(parseFloat(computedStyle.width)) || 0;

    if (store.state.dimensions.width !== newWidth || store.state.dimensions.height !== newHeight) {
      store.set('dimensions', {
        margin: {
          top: params.margin.top,
          right: params.margin.right,
          bottom: params.margin.bottom,
          left: params.margin.left,
        },
        width: params.width ?? newWidth,
        height: params.height ?? newHeight,
        propsWidth: params.width,
        propsHeight: params.height,
      });
    }
    return {
      height: newHeight,
      width: newWidth,
    };
  }, [
    store,
    svgRef,
    params.height,
    params.width,
    // Margin is an object, so we need to include all the properties to prevent infinite loops.
    params.margin.left,
    params.margin.right,
    params.margin.top,
    params.margin.bottom,
  ]);

  useEffectAfterFirstRender(() => {
    const width = params.width ?? store.state.dimensions.width;
    const height = params.height ?? store.state.dimensions.height;
    store.set('dimensions', {
      margin: {
        top: params.margin.top,
        right: params.margin.right,
        bottom: params.margin.bottom,
        left: params.margin.left,
      },
      width,
      height,
      propsHeight: params.height,
      propsWidth: params.width,
    });
  }, [
    store,
    params.height,
    params.width,
    // Margin is an object, so we need to include all the properties to prevent infinite loops.
    params.margin.left,
    params.margin.right,
    params.margin.top,
    params.margin.bottom,
  ]);

  React.useEffect(() => {
    // Ensure the error detection occurs after the first rendering.
    stateRef.current.displayError = true;
  }, []);

  // This effect is used to compute the size of the container on the initial render.
  // It is not bound to the raf loop to avoid an unwanted "resize" event.
  // https://github.com/mui/mui-x/issues/13477#issuecomment-2336634785
  useEnhancedEffect(() => {
    // computeRun is used to avoid infinite loops.
    if (
      hasInSize ||
      !stateRef.current.initialCompute ||
      stateRef.current.computeRun > MAX_COMPUTE_RUN
    ) {
      return;
    }

    const computedSize = computeSize();

    if (computedSize.width !== innerWidth || computedSize.height !== innerHeight) {
      stateRef.current.computeRun += 1;
      if (computedSize.width !== undefined) {
        setInnerWidth(computedSize.width);
      }
      if (computedSize.height !== undefined) {
        setInnerHeight(computedSize.height);
      }
    } else if (stateRef.current.initialCompute) {
      stateRef.current.initialCompute = false;
    }
  }, [innerHeight, innerWidth, computeSize, hasInSize]);

  useEnhancedEffect(() => {
    if (hasInSize) {
      return () => {};
    }
    computeSize();

    const elementToObserve = svgRef.current;
    if (typeof ResizeObserver === 'undefined') {
      return () => {};
    }

    let animationFrame: number;
    const observer = new ResizeObserver(() => {
      // See https://github.com/mui/mui-x/issues/8733
      animationFrame = requestAnimationFrame(() => {
        computeSize();
      });
    });

    if (elementToObserve) {
      observer.observe(elementToObserve);
    }

    return () => {
      if (animationFrame) {
        cancelAnimationFrame(animationFrame);
      }

      if (elementToObserve) {
        observer.unobserve(elementToObserve);
      }
    };
  }, [computeSize, hasInSize, svgRef]);

  if (process.env.NODE_ENV !== 'production') {
    if (stateRef.current.displayError && params.width === undefined && innerWidth === 0) {
      console.error(
        `MUI X Charts: ChartContainer does not have \`width\` prop, and its container has no \`width\` defined.`,
      );
      stateRef.current.displayError = false;
    }
    if (stateRef.current.displayError && params.height === undefined && innerHeight === 0) {
      console.error(
        `MUI X Charts: ChartContainer does not have \`height\` prop, and its container has no \`height\` defined.`,
      );
      stateRef.current.displayError = false;
    }
  }

  const drawingArea = useSelector(store, selectorChartDrawingArea);
  const isXInside = React.useCallback(
    (x: number) => x >= drawingArea.left - 1 && x <= drawingArea.left + drawingArea.width,
    [drawingArea.left, drawingArea.width],
  );

  const isYInside = React.useCallback(
    (y: number) => y >= drawingArea.top - 1 && y <= drawingArea.top + drawingArea.height,
    [drawingArea.height, drawingArea.top],
  );
  const isPointInside = React.useCallback(
    (x: number, y: number, targetElement?: Element | EventTarget | null) => {
      // For element allowed to overflow, wrapping them in <g data-drawing-container /> make them fully part of the drawing area.
      if (
        targetElement &&
        'closest' in targetElement &&
        targetElement.closest('[data-drawing-container]')
      ) {
        return true;
      }

      return isXInside(x) && isYInside(y);
    },
    [isXInside, isYInside],
  );

  return { instance: { isPointInside, isXInside, isYInside } };
};

useChartDimensions.params = {
  width: true,
  height: true,
  margin: true,
};

useChartDimensions.getDefaultizedParams = ({ params }) => ({
  ...params,
  margin: defaultizeMargin(params.margin, DEFAULT_MARGINS),
});

useChartDimensions.getInitialState = ({ width, height, margin }) => {
  return {
    dimensions: {
      margin,
      width: width ?? 0,
      height: height ?? 0,
      propsWidth: width,
      propsHeight: height,
    },
  };
};
